
/*
This file is part of MJIN2.

Copyright (C) 2012-2013 Opensource Game Studio

This software is provided 'as-is', without any express or implied
warranty.  In no event will the authors be held liable for any damages
arising from the use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not
   claim that you wrote the original software. If you use this software
   in a product, an acknowledgment in the product documentation would be
   appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
   misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/

#include <mjin2/Camera.h>
#include <mjin2/InputListener.h>
#include <mjin2/Scene.h>
#include <mjin2/WindowListener.h>
#include <mjin2/priv/Camera.h>
#include <mjin2/priv/Common.h>
#include <mjin2/priv/Input.h>
#include <mjin2/priv/Scene.h>
#include <mjin2/priv/Window.h>

#include <osg/Camera>
#include <osgShadow/ShadowedScene>
#include <osgViewer/GraphicsWindow>
#include <osgViewer/ViewerEventHandlers>

namespace mjin2
{
namespace priv
{

Window::Window(const String &title,
               const WindowConfig &wc) throw () :
    mCamera(0),
    mWC(wc),
    mScreenW(0),
    mScreenH(0),
    mScene(0),
    mIsRunning(false),
    mTitle(title)
{
    mWindowClose      = new WindowClose(this);
    mWindowGeometry   = new WindowGeometry(this);
    mWindowRecreation = new WindowRecreation(this);

    mViewer = new Viewer;
    mInput = new priv::Input;
    setConfigRecreate(wc);
    mViewer->addEventHandler(mInput);
    mViewer->addEventHandler(mWindowClose);
    // Disable Viewer from quitting on ESC.
    mViewer->setKeyEventSetsDone(0);
    mViewer->setRealizeOperation(mWindowRecreation);
}

Window::~Window() throw ()
{
    restoreScreenResolution();
    delete mCamera;
    delete mInput;
    mViewer->ref();
}

void Window::addInputListener(InputListener *listener) throw ()
{
    mInput->addListener(listener);
}

void Window::addWindowListener(WindowListener *listener) throw ()
{
    mListeners.push_back(listener);
}

mjin2::Camera *Window::camera() throw ()
{
    return mCamera;
}

WindowConfig Window::config() const throw ()
{
    return mWC;
}

Vec4 Window::geometry() const throw ()
{
    return mGeometry;
}

void Window::removeInputListener(InputListener *listener) throw ()
{
    mInput->removeListener(listener);
}

void Window::removeWindowListener(WindowListener *listener) throw ()
{
    for (auto it = mListeners.begin(); it != mListeners.end(); ++it)
        if (*it == listener)
        {
            mListeners.erase(it);
            break;
        }
}

void Window::reportClose() throw ()
{
    stop();
    for (auto it = mListeners.begin(); it != mListeners.end(); ++it)
        (*it)->onWindowClose();
}

void Window::reportGeometry(const Vec4 &g) throw ()
{
    mGeometry = g;
    mWC.width  = g.z;
    mWC.height = g.w;
    mInput->setWindowHeight(g.w);
    for (auto it = mListeners.begin(); it != mListeners.end(); ++it)
        (*it)->onWindowGeometry(g);
}

void Window::reportRecreation() throw ()
{
    osg::ref_ptr<osg::GraphicsContext> gc =
        mCamera->mD->camera()->getGraphicsContext();
    u32 contextID = gc->getState()->getContextID();
    static bool hasVideoInfo = false;
    if (!hasVideoInfo)
    {
        mVI = priv::videoInfo(osg::GL2Extensions::Get(contextID, true), contextID);
        hasVideoInfo = true;
    }
    for (auto it = mListeners.begin(); it != mListeners.end(); ++it)
        (*it)->onWindowRecreation();
}

void Window::run() throw ()
{
    mIsRunning = true;
    while (mIsRunning)
        startViewer();
}

void Window::setConfig(const WindowConfig &wc) throw ()
{
    if (configRecreate(wc))
        setConfigRecreate(wc);
    else
        setConfigKeep(wc);
    mWC = wc;
}

void Window::setMousePosition(s32 x, s32 y) throw ()
{
    mViewer->requestWarpPointer(x, y);
}

void Window::setScene(mjin2::Scene *scene) throw ()
{
    if (scene)
        mViewer->setSceneData(scene->mD->scene());
    else
        mViewer->setSceneData(0);
    mScene = scene;
}

void Window::setTimer(mjin2::Timer *timer) throw ()
{
    mViewer->setTimer(timer);
}

void Window::setTitle(const String &title) throw ()
{
    osgViewer::GraphicsWindow *gw = dynamic_cast<osgViewer::GraphicsWindow *>(
        mCamera->mD->camera()->getGraphicsContext());
    gw->setWindowName(mTitle);
    mTitle = title;
}

void Window::stop() throw ()
{
    mIsRunning = false;
    stopViewer();
}

String Window::title() const throw ()
{
    return mTitle;
}

VideoInfo Window::videoInfo() const throw () {
    return mVI;
}

void Window::center(u32 width, u32 height, u32 &x, u32 &y) throw ()
{
    u32 screenW;
    u32 screenH;
    osg::GraphicsContext::getWindowingSystemInterface()->getScreenResolution(
        osg::GraphicsContext::ScreenIdentifier(0),
        screenW,
        screenH);
    x = screenW / 2 - width / 2;
    y = screenH / 2 - height / 2;
}

bool Window::configRecreate(const WindowConfig &wc) throw ()
{
    return (mWC.vsync        != wc.vsync ||
            mWC.antiAliasing != wc.antiAliasing);
}

void Window::restoreScreenResolution() throw ()
{
    if (mScreenW && mScreenH)
    {
        setScreenResolution(mScreenW, mScreenH);
        mScreenW = 0;
        mScreenH = 0;
    }
}

void Window::setConfigKeep(const WindowConfig &wc) throw ()
{
    if (mWC.fps != wc.fps)
        mViewer->setRunMaxFrameRate(wc.fps);
    osgViewer::GraphicsWindow *gw = dynamic_cast<osgViewer::GraphicsWindow *>(
        mCamera->mD->camera()->getGraphicsContext());
    if (mWC.width      != wc.width  ||
        mWC.height     != wc.height ||
        mWC.fullScreen != wc.fullScreen)
    {
        restoreScreenResolution();
        gw->setWindowDecoration(!wc.fullScreen);
        u32 x = 0;
        u32 y = 0;
        if (wc.fullScreen)
            setScreenResolution(wc.width, wc.height);
        else
            center(wc.width, wc.height, x, y);
        gw->setWindowRectangle(x, y, wc.width, wc.height);
    }
}

void Window::setConfigRecreate(const WindowConfig &wc) throw ()
{
    mViewer->setRunMaxFrameRate(wc.fps);
    if (mIsRunning)
        stopViewer();
    restoreScreenResolution();
    if (wc.fullScreen)
        setScreenResolution(wc.width, wc.height);
    osg::ref_ptr<osg::GraphicsContext::Traits> traits =
        new osg::GraphicsContext::Traits;
    u32 x = 0;
    u32 y = 0;
    if (!wc.fullScreen)
        center(wc.width, wc.height, x, y);
    traits->x                = x;
    traits->y                = y;
    traits->windowName       = mTitle;
    traits->width            = wc.width;
    traits->height           = wc.height;
    traits->windowDecoration = !wc.fullScreen;
    traits->doubleBuffer     = true;
    traits->samples          = wc.antiAliasing;
    traits->vsync            = wc.vsync;
    osg::ref_ptr<osg::GraphicsContext> gc =
        osg::GraphicsContext::createGraphicsContext(traits.get());
    osg::ref_ptr<osg::Camera> cam;
    // Later runs.
    if (mCamera)
        cam = mCamera->mD->camera();
    // First run.
    else
    {
        cam = new osg::Camera;
        mCamera = new mjin2::Camera(new priv::Camera(cam));
        mViewer->setCamera(cam);
        mViewer->setCameraManipulator(mCamera->mD->manipulator());
    }
    cam->setGraphicsContext(gc);
    cam->getGraphicsContext()->setResizedCallback(mWindowGeometry);
    cam->setViewport(new osg::Viewport(0, 0, traits->width, traits->height));
    cam->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    cam->setProjectionMatrixAsPerspective(
        30,
        static_cast<double>(traits->width) / static_cast<double>(traits->height),
        1,
        1000);
    reportGeometry(Vec4(x, y, wc.width, wc.height));
}

void Window::setScreenResolution(u32 width, u32 height) throw ()
{
    osg::GraphicsContext::WindowingSystemInterface *wsi =
        osg::GraphicsContext::getWindowingSystemInterface();
    // Record original screen resolution.
    if (!mScreenW && !mScreenH)
        wsi->getScreenResolution(osg::GraphicsContext::ScreenIdentifier(0),
                                 mScreenW,
                                 mScreenH);
    wsi->setScreenResolution(osg::GraphicsContext::ScreenIdentifier(0),
                             width,
                             height);
}

void Window::startViewer() throw ()
{
    mViewer->setDone(false);
    mViewer->run();
}

void Window::stopViewer() throw ()
{
    mViewer->stopThreading();
    mViewer->setDone(true);
}

} // namespace priv
} // namespace mjin2

